/*
 * Copyright 2009 Zero Separation
 *
 *     This file is part of PDSUtilities.
 *
 *  PDSUtilities is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  PDSUtilities is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with PDSUtilities.  If not, see <http://www.gnu.org/licenses/>.
 *
 */


package com.zero_separation.pds.utilities.referencecollections;

import java.io.Serializable;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

/**
 * This is an abstract implementation of a Map that will automatically
 * reference and dereference ManagedObjects placed inside. In order to create
 * a new fully featured ReferenceCollection with a different storage mechanism
 * just extend this class and implement the abstract method along with whichever
 * constructors are required.
 *
 * ManagedObjects can be used as keys into this Map and will be correctly
 * referenced and dereferenced. Note that this means that a ManagedReference to
 * each object will be created and the Reference is what is used to find the
 * correct value, not the Managed Object itself.
 *
 * @author Tim Boura - Zero Separation
 */
public abstract class ReferenceAbstractMap<K, V> extends ReferenceAbstract implements Map<K, V> {
    /** The version of the serialized form of this class. */
    private static final long serialVersionUID = 1L;

    /**
     * Override this abstract method to return the underlying map used to store
     * the data.
     *
     * @return An implementation of Map that can be used to store and query the
     * data within this ReferenceMap.
     */
    protected abstract Map<Object,Object> getInternalMap();

    public int size() {
        return getInternalMap().size();
    }

    public boolean isEmpty() {
        return getInternalMap().isEmpty();
    }

    public boolean containsKey(Object key) {
        return getInternalMap().containsKey(referenceIfNeeded(key));
    }

    public boolean containsValue(Object value) {
        return getInternalMap().containsValue(referenceIfNeeded(value));
    }

    public V get(Object key) {
        return (V)dereferenceIfNeeded(getInternalMap().get(referenceIfNeeded(key)));
    }

    public V put(K key, V value) {
        return (V)dereferenceIfNeeded(getInternalMap().put(referenceIfNeeded(key), referenceIfNeeded(value)));
    }

    public V remove(Object key) {
        return (V)dereferenceIfNeeded(getInternalMap().remove(referenceIfNeeded(key)));
    }

    public void putAll(Map<? extends K, ? extends V> m) {
        for (Entry<? extends K, ? extends V> e: m.entrySet())
            put(e.getKey(), e.getValue());
    }

    public void clear() {
        getInternalMap().clear();
    }

    public Set<K> keySet() {
        return new ReferenceSet(getInternalMap().keySet());
    }

    public Collection<V> values() {
        return new ReferenceCollection(getInternalMap().values());
    }

    public Set<Entry<K, V>> entrySet() {
        return new ReferenceEntrySet(getInternalMap().entrySet());
    }

    /**
     * An implementation of Entry<K,V> that is aware of ManagedObjects and will
     * reference and dereference them as required.
     *
     * @param <K> - The type of the Key paramater
     * @param <V> - The type of the Value paramater
     */
    private static class ReferenceEntry<K,V> extends ReferenceAbstract implements Entry<K, V> {
        /** The version of the serialized form of this class. */
        private static final long serialVersionUID = 1L;

        /**
         * The entry being accessed is stored internally and dereferenced on demand.
         */
        Entry internalEntry;

        /**
         * The internal constructor.
         *
         * @param internalEntry The entry to wrap
         */
        private ReferenceEntry(Entry internalEntry) {
            this.internalEntry = internalEntry;
        }

        private Entry getInternalEntry() {
            return internalEntry;
        }

        public K getKey() {
            return (K)dereferenceIfNeeded(internalEntry.getKey());
        }

        public V getValue() {
            return (V)dereferenceIfNeeded(internalEntry.getValue());
        }

        public V setValue(V value) {
            return (V)dereferenceIfNeeded(internalEntry.setValue(referenceIfNeeded(value)));
        }

    }

    /**
     * An iterator used to iterate over the contents of a ReferenceMap and wrap each
     * returned entry in a ReferenceEntry to handle referencing and dereferencing
     * as required. Note that no guarantees can be made about serialization stability,
     * in particular this must be used entirely within one Transaction or stored in
     * the same ManagedObject as the ReferenceMap being iterated.
     *
     * @param <K> - The type of the Key paramater
     * @param <V> - The type of the Value paramater
     */
    public static class ReferenceEntryIterator<K,V> implements Iterator<Entry<K, V>>, Serializable {
        /** The version of the serialized form of this class. */
        private static final long serialVersionUID = 1L;

        /**
         * The iterator being accessed is stored internally and then Entry values
         * are wrapped on demand.
         */
        private Iterator<Entry> internalIterator;

        private ReferenceEntryIterator(Iterator<Entry> internalIterator) {
            this.internalIterator = internalIterator;
        }

        public boolean hasNext() {
            return internalIterator.hasNext();
        }

        public Entry<K, V> next() {
            return new ReferenceEntry(internalIterator.next());
        }

        public void remove() {
            throw new UnsupportedOperationException("Not supported yet.");
        }

    }

    /**
     * A wrapper for the EntrySet on a mapping. This class wraps the internal
     * EntrySet and then wraps and unwraps objects on demand as the enter and
     * leave. This provides a read-only view into the mapping.
     *
     * @param <K> - The type of the Key paramater
     * @param <V> - The type of the Value paramater
     */
    private static class ReferenceEntrySet<K, V> extends ReferenceAbstract implements Set<Entry<K, V>> {
        /** The version of the serialized form of this class. */
        private static final long serialVersionUID = 1L;

        /**
         * The set being accessed is stored internally and then Entry values
         * are wrapped on demand.
         */
        Set<Entry> internalSet;

        private ReferenceEntrySet(Set<Entry> internalSet) {
            this.internalSet = internalSet;
        }

        public int size() {
            return internalSet.size();
        }

        public boolean isEmpty() {
            return internalSet.isEmpty();
        }

        public boolean contains(Object o) {
            if (o instanceof ReferenceEntry)
                o = ((ReferenceEntry)o).getInternalEntry();
            return internalSet.contains(o);
        }

        public Iterator<Entry<K, V>> iterator() {
            return new ReferenceEntryIterator(internalSet.iterator());
        }

        public Object[] toArray() {
            Object[] array = internalSet.toArray();
            for (int i=0;i<array.length;i++)
                array[i] = new ReferenceEntry((Entry)array[i]);
            return array;
        }

        public <T> T[] toArray(T[] a) {
            T[] array = (T[]) internalSet.toArray(a);
            for (int i=0;i<array.length;i++)
                array[i] = (T)new ReferenceEntry((Entry)array[i]);
            return array;
        }

        public boolean add(Entry<K, V> e) {
            throw new UnsupportedOperationException("Not supported yet.");
        }

        public boolean remove(Object o) {
            throw new UnsupportedOperationException("Not supported yet.");
        }

        public boolean containsAll(Collection<?> c) {
            for (Object o: c)
                if (!contains(o))
                    return false;

            return true;
        }

        public boolean addAll(Collection<? extends Entry<K, V>> c) {
            throw new UnsupportedOperationException("Not supported yet.");
        }

        public boolean retainAll(Collection<?> c) {
            throw new UnsupportedOperationException("Not supported yet.");
        }

        public boolean removeAll(Collection<?> c) {
            throw new UnsupportedOperationException("Not supported yet.");
        }

        public void clear() {
            throw new UnsupportedOperationException("Not supported yet.");
        }

    };
}
